package com.hsgene.common.util.date;

import com.google.common.base.Strings;
import com.hsgene.common.util.exception.IllegalParamException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 简单的日期工具类
 *
 * @author wangbing
 * @version 1.0, 2018/7/25
 */
public class SimpleDateUtils {

    private static final Logger logger = LogManager.getLogger(SimpleDateUtils.class);

    /**
     * 保存pattern对应的日期时间格式化器
     */
    private final static Map<String, SimpleDateFormat> FORMAT_MAP = new HashMap<>();

    /**
     * 计算两个日期时间之间相隔的天数
     * PS：
     * before 小于 after 返回正数
     * before 大于 after 返回负数
     *
     * @param before 第一个日期时间
     * @param after  第二个日期时间
     * @return 间隔天数
     */
    public static int durationDays(LocalDateTime before, LocalDateTime after) {
        if (before == null || after == null) {
            return 0;
        }
        Duration duration = Duration.between(before, after);

        return (int) duration.toDays();
    }

    /**
     * 计算两个日期时间之间相隔的天数
     * PS：
     * before 小于 after 返回正数
     * before 大于 after 返回负数
     *
     * @param before 第一个日期时间
     * @param after  第二个日期时间
     * @return 间隔天数
     */
    public static int durationDays(Date before, Date after) {
        if (before == null || after == null) {
            return 0;
        }

        return durationDays(LocalDateTime.ofInstant(before.toInstant(), ZoneId.systemDefault()),
                LocalDateTime.ofInstant(after.toInstant(), ZoneId.systemDefault()));
    }

    /**
     * 计算两个日期时间之间相隔的描述
     * PS：
     * before 小于 after 返回正数
     * before 大于 after 返回负数
     *
     * @param before 第一个日期时间
     * @param after  第二个日期时间
     * @return 间隔天数
     */
    public static long durationSeconds(LocalDateTime before, LocalDateTime after) {
        if (before == null || after == null) {
            return 0;
        }
        Duration duration = Duration.between(before, after);

        return duration.toMillis() / 1000;
    }

    /**
     * 判断指定时间是否是当天.
     *
     * @param date
     * @return
     */
    public static boolean isToday(Date date) {
        return getDateOfMidNight().equals(getDate(date, HourMinSecondFormat.MID_NIGHT));
    }

    /**
     * 获取表示当天凌晨(即00:00:00)的日期对象.
     *
     * @return
     */
    public static Date getDateOfMidNight() {
        return getDate(new Date(), HourMinSecondFormat.MID_NIGHT);
    }

    /**
     * 获取表示当天凌晨(即00:00:00)的日期对象.
     *
     * @param date 指定日期
     * @return
     */
    public static Date getDateOfMidNight(Date date) {
        return getDate(date, HourMinSecondFormat.MID_NIGHT);
    }

    /**
     * 获取表示当天截止(即23:59:59)的日期对象.
     *
     * @return
     */
    public static Date getDateOfTodayEnd() {
        return getDate(new Date(), HourMinSecondFormat.TODAY_END);
    }

    /**
     * 获取表示当天截止(即23:59:59)的日期对象.
     *
     * @param date 指定日期
     * @return
     */
    public static Date getDateOfTodayEnd(Date date) {
        return getDate(date, HourMinSecondFormat.TODAY_END);
    }

    /**
     * 解析日期字符串到Date类型.
     *
     * @param dateStr 需要转换的String日期
     * @param pattern 日期字符串的格式，如：yyyy-MM-dd
     * @return
     */
    public static Date parseDate(String dateStr, String pattern) {
        return parseDate(dateStr, pattern, null);
    }

    /**
     * 解析日期字符串到Date类型.
     *
     * @param dateStr 需要转换的String日期
     * @param pattern 日期字符串的格式，如：yyyy-MM-dd
     * @param format  设置时分秒的格式化定义（可空）
     *                <br>比如MID_NIGHT表示将指定日期格式化到当天凌晨(00:00:00)</br>
     *                <br>比如TODAY_END表示将指定日期格式化到当天截止(23:59:59)</br>
     * @return
     */
    public static Date parseDate(String dateStr, String pattern, HourMinSecondFormat format) {
        if (Strings.isNullOrEmpty(dateStr) || Strings.isNullOrEmpty(pattern)) {
            return null;
        }

        Date date;
        SimpleDateFormat sdf = getSimpleDateFormat(pattern);
        try {
            date = sdf.parse(dateStr);
        } catch (ParseException e) {
            throw new IllegalParamException(
                    "日期转换失败, dateStr与pattern不匹配. dateStr = " + dateStr + "  pattern = " + pattern);
        }

        return getDate(date, format);
    }

    /**
     * 获取pattern对应的格式化器
     *
     * @param pattern 日期字符串的格式
     * @return
     */
    public static SimpleDateFormat getSimpleDateFormat(String pattern) {
        SimpleDateFormat sdf = FORMAT_MAP.get(pattern);
        if (sdf == null) {
            sdf = new SimpleDateFormat(pattern);
            FORMAT_MAP.put(pattern, sdf);
        }
        return sdf;
    }

    private static Date getDate(Date date, HourMinSecondFormat format) {
        if (format == null) {
            return date;
        }
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        switch (format) {
            case MID_NIGHT:
                cal.set(Calendar.HOUR_OF_DAY, 0);
                cal.set(Calendar.MINUTE, 0);
                cal.set(Calendar.SECOND, 0);
                cal.set(Calendar.MILLISECOND, 0);
                break;
            case TODAY_END:
                cal.set(Calendar.HOUR_OF_DAY, 23);
                cal.set(Calendar.MINUTE, 59);
                cal.set(Calendar.SECOND, 59);
                cal.set(Calendar.MILLISECOND, 0);
                break;
            default:
                break;
        }
        return cal.getTime();
    }

    /**
     * 格式化时间
     *
     * @param date    日期时间对象
     * @param pattern 格式化类型
     * @return 日期时间字符串
     */
    public static String formatDate(Date date, DatePattern pattern) {
        if (date == null) {
            return null;
        }
        if (pattern == null) {
            pattern = DatePattern.ISO_PATTERN;
        }
        return getSimpleDateFormat(pattern.toString()).format(date);
    }

    /**
     * 将日期格式2018-07-12T01:40:00.000+0000转成2018-07-12 09:40:00
     *
     * @param dateStr 日期
     * @return
     */
    public static String strToDateFormat(String dateStr) {
        try {
            Date date = getSimpleDateFormat(DatePattern.ISO_PATTERN.toString()).parse(dateStr);
            return formatDate(date, DatePattern.STANDARD_PATTERN);
        } catch (Exception e) {
            logger.error("date format is error ", e);
        }
        return dateStr;
    }

    public static String dateFormatToStr(Date date) {
        try {
            return formatDate(date, DatePattern.STANDARD_PATTERN);
        } catch (Exception e) {
            logger.error("date format is error ", e);
        }
        return null;
    }

    public static String dateToTimestamp(String s) throws ParseException {
        String res;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = simpleDateFormat.parse(s);
        long ts = date.getTime();
        res = String.valueOf(ts);
        return res;
    }

    /**
     * 常用的日期模式定义.
     */
    public enum DatePattern {
        /**
         * yyyyMMdd
         */
        YYYYMMDD("yyyyMMdd"),
        /**
         * yyyy-MM-dd
         */
        YYYY_MM_DD("yyyy-MM-dd"),
        /**
         * yyyy
         */
        YYYY("yyyy"),
        /**
         * yyyy-MM-dd HH:mm:ss
         */
        STANDARD_PATTERN("yyyy-MM-dd HH:mm:ss"),
        /**
         * yyyy-MM-dd'T'HH:mm:ss.SSSZ
         */
        ISO_PATTERN("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

        private final String value;

        DatePattern(final String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return this.value;
        }

    }

    /**
     * 时分秒格式化定义.
     */
    public enum HourMinSecondFormat {
        /**
         * 凌晨零点, 即00:00:00
         */
        MID_NIGHT,
        /**
         * 当天截止，即23:59:59
         */
        TODAY_END
    }


    /**
     * 两个时间之间相差距离多少天
     *
     * @param str1 时间参数 1：
     * @param str2 时间参数 2：
     * @return 相差天数
     */
    public static long getDistanceDays(String str1, String str2) throws Exception {
        DateFormat df = getSimpleDateFormat("yyyy-MM-dd");
        Date one;
        Date two;
        long days = 0;
        try {
            one = df.parse(str1);
            two = df.parse(str2);
            long time1 = one.getTime();
            long time2 = two.getTime();
            long diff;
            if (time1 < time2) {
                diff = time2 - time1;
            } else {
                diff = time1 - time2;
            }
            days = diff / (1000 * 60 * 60 * 24);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return days;
    }

    /**
     * 两个时间相差距离多少天多少小时多少分多少秒
     *
     * @param str1 时间参数 1 格式：1990-01-01 12:00:00
     * @param str2 时间参数 2 格式：2009-01-01 12:00:00
     * @return long[] 返回值为：{天, 时, 分, 秒}
     */
    public static long[] getDistanceTimes(String str1, String str2) {
        DateFormat df = getSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date one;
        Date two;
        long day = 0;
        long hour = 0;
        long min = 0;
        long sec = 0;
        try {
            one = df.parse(str1);
            two = df.parse(str2);
            long time1 = one.getTime();
            long time2 = two.getTime();
            long diff;
            if (time1 < time2) {
                diff = time2 - time1;
            } else {
                diff = time1 - time2;
            }
            day = diff / (24 * 60 * 60 * 1000);
            hour = (diff / (60 * 60 * 1000) - day * 24);
            min = ((diff / (60 * 1000)) - day * 24 * 60 - hour * 60);
            sec = (diff / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        long[] times = {day, hour, min, sec};
        return times;
    }

    /**
     * 两个时间相差距离多少天多少小时多少分多少秒
     *
     * @param str1 时间参数 1 格式：1990-01-01 12:00:00
     * @param str2 时间参数 2 格式：2009-01-01 12:00:00
     * @return String 返回值为：xx天xx小时xx分xx秒
     */
    public static String getDistanceTime(String str1, String str2) {
        DateFormat df = getSimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date one;
        Date two;
        long day = 0;
        long hour = 0;
        long min = 0;
        long sec = 0;
        try {
            one = df.parse(str1);
            two = df.parse(str2);
            long time1 = one.getTime();
            long time2 = two.getTime();
            long diff;
            if (time1 < time2) {
                diff = time2 - time1;
            } else {
                diff = time1 - time2;
            }
            day = diff / (24 * 60 * 60 * 1000);
            hour = (diff / (60 * 60 * 1000) - day * 24);
            min = ((diff / (60 * 1000)) - day * 24 * 60 - hour * 60);
            sec = (diff / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return day + "天" + hour + "小时" + min + "分" + sec + "秒";
    }

    /**
     * 两个时间相差距离多少天多少小时多少分多少秒
     *
     * @param one
     * @param two
     * @return String 返回值为：xx天xx小时xx分xx秒
     */
    public static Long[] getDistanceTime(Date one, Date two) {
        Long[] dates = new Long[4];
        long day;
        long hour;
        long min;
        long sec;
        long time1 = one.getTime();
        long time2 = two.getTime();
        long diff;
        if (time1 < time2) {
            diff = time2 - time1;
            day = diff / (24 * 60 * 60 * 1000);
            hour = (diff / (60 * 60 * 1000) - day * 24);
            min = ((diff / (60 * 1000)) - day * 24 * 60 - hour * 60);
            sec = (diff / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - min * 60);
            dates[0] = day;
            dates[1] = hour;
            dates[2] = min;
            dates[3] = sec;
        } else {
            dates[0] = 0L;
            dates[1] = 0L;
            dates[2] = 0L;
            dates[3] = 0L;
        }
        return dates;
    }


    /**
     * 时间日期+1
     *
     * @param dateStr
     * @return
     */
    public static String dateAddOne(String dateStr) {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse(dateStr);
            Calendar c = Calendar.getInstance();
            c.setTime(date);
            c.add(Calendar.DAY_OF_MONTH, 1);
            dateStr = sdf.format(c.getTime());
        } catch (Exception e) {
            logger.error("date add one is error...", e);
        }
        return dateStr;
    }

    /**
     * 往后推day个自然日
     *
     * @param day 推迟天数
     * @return 推迟后的日期
     */
    public static LocalDate plusNaturalDay(int day) {
        return plusWordDay(LocalDate.now(), day);
    }

    /**
     * 往后推day个自然日
     *
     * @param begin 开始日期
     * @param day   推迟天数
     * @return 推迟后的日期
     */
    public static LocalDate plusNaturalDay(LocalDate begin, int day) {
        return begin.plusDays(day + 1);
    }

    /**
     * 往后推day个工作日
     *
     * @param day 推迟天数
     * @return 推迟后的日期
     */
    public static LocalDate plusWordDay(int day) {
        return plusWordDay(LocalDate.now(), day);
    }

    /**
     * 往后推day个工作日
     *
     * @param begin 开始日期
     * @param day   推迟天数
     * @return 推迟后的日期
     */
    public static LocalDate plusWordDay(LocalDate begin, int day) {
        DayOfWeek dayOfWeek = begin.getDayOfWeek();
        // 开始当天不算
        if (DayOfWeek.FRIDAY.equals(dayOfWeek)) {
            begin = begin.plusDays(3);
        } else if (DayOfWeek.SATURDAY.equals(dayOfWeek)) {
            begin = begin.plusDays(2);
        } else if (DayOfWeek.SUNDAY.equals(dayOfWeek)) {
            begin = begin.plusDays(1);
        } else {
            begin = begin.plusDays(1);
        }
        DayOfWeek beginDayOfWeek = begin.getDayOfWeek();
        int beginWeekResidualWorkDay = 6 - beginDayOfWeek.getValue();
        if (day > beginWeekResidualWorkDay) {
            return plusWordDay(begin.plusDays(beginWeekResidualWorkDay + 2 - 1), day - beginWeekResidualWorkDay);
        } else {
            return begin.plusDays(day);
        }
    }
}
